import { User } from '@/model/User'
import cron from 'node-cron'
import { TokenData } from './token'
import { dataSource } from './typeormConnect'
import { State } from './utils'

interface BaseItem {
    time: number
}

enum CacheState {
    expired = State.expired,
    unexpired = State.unexpired,
    unknown = State.unknown
}

const userRepository = dataSource.getRepository(User)

abstract class BaseExpireCheck {
    constructor(private name: string, protected expirationTime: number) {
        this.clear()
    }
    protected map: Map<unknown, BaseItem> = new Map()

    /**
     * 在每天的3:00清除数据
     */
    private clear() {
        // 一天的毫秒数，清空一天前的数据
        const dayMillisecond = 1000 * 60 * 60 * 24
        // https://crontab.guru/
        cron.schedule('0 3 * * *', () => {
            const orgLength = this.map.size
            const expireTime = new Date().getTime() - dayMillisecond
            this.map.forEach((value, key, map) => {
                if (value.time < expireTime) map.delete(key)
            })
            console.log(`${this.name}已清除数据：${orgLength - this.map.size}；剩余：${this.map.size}`)
        })
    }

    /**
     * 判断一个时间是否过期
     * @param {number} expTime 过期的时间
     * @param {number} time 需要比对的时间
     */
    protected judgeExpire(expirationTime: number | undefined, time: number) {
        const expTime = expirationTime ?? this.expirationTime ?? 0
        if (expTime || new Date().getTime() - time > expTime) {
            return CacheState.expired
        } else {
            return CacheState.unexpired
        }
    }

    /**
     * 从缓存变量里查询
     */
    protected abstract checkCache(param: unknown, expirationTime?: number): CacheState
    /**
     * 从数据库里查询
     */
    protected abstract checkDatabase(param: unknown, expirationTime?: number): Promise<CacheState>

    /**
     * 查询主入口
     */
    public check(param: unknown, expirationTime?: number): Promise<CacheState> {
        const cacheResult = this.checkCache(param, expirationTime)
        if (cacheResult === CacheState.unknown) {
            return this.checkDatabase(param, expirationTime)
        } else {
            return Promise.resolve(cacheResult)
        }
    }

    public add(param: Record<string, any>, key: unknown): Promise<any> {
        this.map.set(key, {
            ...param,
            time: new Date().getTime()
        })
        return Promise.resolve()
    }
}


interface TokenItem extends BaseItem {
    userId: number,
    token: string
}

interface TokenDataWithToken extends TokenData {
    token: string
}

class TokenCheck extends BaseExpireCheck {
    protected map: Map<number, TokenItem> = new Map()
    constructor() {
        super('token', 0)
    }
    protected checkCache({ userId, token }: TokenDataWithToken): CacheState {
        const find = this.map.get(userId)
        if (find) {
            return find.token === token ? CacheState.unexpired : CacheState.expired
        } else {
            return CacheState.unknown
        }
    }
    protected checkDatabase({ userId, token }: TokenDataWithToken): Promise<CacheState> {
        return new Promise((resolve, reject) => {
            userRepository
                .createQueryBuilder()
                .select()
                .where('id = :userId', { userId })
                .getOne()
                .then(r => {
                    if (!r) return reject('未查询到数据')
                    resolve(r.token === token ? CacheState.unexpired : CacheState.expired)
                }).catch((e: any) => {
                    reject(e)
                })
        })
    }

    public check(param: { userId: number, token: string }): Promise<CacheState> {
        return super.check(param)
    }

    public add(param: TokenDataWithToken) {
        super.add(param, param.userId)
        return userRepository.createQueryBuilder().update(User).set({ token: param.token }).where('id = :userId', { userId: param.userId }).execute()
    }
}

interface SendEmailParam {
    toEmail: string,
    code: string
}
interface SendEmailItem extends BaseItem, SendEmailParam {}
class SendEmailCheck extends BaseExpireCheck {

    protected map: Map<string, SendEmailItem> = new Map()

    /**
     * Creates an instance of SendEmailCheck.
     * @param {number} expirationTime 过期时间
     * @param {number} coolingTime 冷却时间
     * @memberof SendEmailCheck
     */
    constructor(expirationTime: number, private coolingTime: number) {
        super('SendEmail', expirationTime)
    }

    protected checkCache(toEmail: string, expirationTime?: number | undefined): CacheState {
        const find = this.map.get(toEmail)
        if (find) {
            return this.judgeExpire(expirationTime, find.time)
        } else {
            return CacheState.unknown
        }
    }

    protected checkDatabase(): Promise<CacheState> {
        return Promise.resolve(CacheState.expired)
    }

    public check(toEmail: string): Promise<CacheState> {
        return super.check(toEmail)
    }

    public add(param: SendEmailParam): Promise<any> {
        super.add(param, param.toEmail)
        return Promise.resolve()
    }

    public checkCooling(toEmail: string) {
        const now = new Date().getTime()
        const flag = (() => {
            const item = this.map.get(toEmail)
            if (!item) return false
            return now - item.time < this.coolingTime
        })()
        return flag ? CacheState.unexpired : CacheState.expired
    }

    public checkCode(toEmail: string, code: string) {
        const now = new Date().getTime()
        const flag = (() => {
            const item = this.map.get(toEmail)
            if (!item) return false
            return (now - item.time < this.expirationTime) && (item.code === code)
        })()
        return flag ? State.available : State.unavailable
    }
}

export {
    CacheState,
    BaseExpireCheck,
    TokenCheck,
    SendEmailCheck
}